using Microsoft.AspNetCore.Http;
using System.Security.Cryptography;

namespace HZY.Core.UploadFile;

public static class FileManagerContextExtensions
{
    public static string GetFileRoot(this FileManagerContext context, string fileName)
    {
        if (string.IsNullOrWhiteSpace(context.FileDir))
        {
            throw new Exception("文件保存路径不能为空!");
        }

        return Path.Combine(context.FileDir, fileName);
    }

    public static (List<Guid> deleteFiles, Exception? ex) FileDelete(this FileManagerContext context, IEnumerable<(Guid, string)> fileNames)
    {
        if (string.IsNullOrWhiteSpace(context.FileDir))
        {
            throw new Exception("文件保存路径不能为空!");
        }

        var deleteFiles = new List<Guid>();
        try
        {
            foreach (var fileName in fileNames)
            {
                if (string.IsNullOrWhiteSpace(fileName.Item2))
                {
                    deleteFiles.Add(fileName.Item1);
                    continue;
                }

                var filePath = Path.Combine(context.FileDir, fileName.Item2);

                File.Delete(filePath);

                deleteFiles.Add(fileName.Item1);
            }
        }
        catch (Exception e)
        {
            return (deleteFiles, e);
        }

        return (deleteFiles, null);
    }

    public static async Task<FileContextInfo> FileUploadAsync(this FileManagerContext context, IFormFile? formFile)
    {
        var fileContextInfo = new FileContextInfo();
        try
        {
            if (formFile == null)
            {
                fileContextInfo.State = FileContextStates.NoneFile;
                fileContextInfo.Exception = new Exception("文件不能为空!");
                return fileContextInfo;
            }

            if (string.IsNullOrWhiteSpace(context.FileDir))
            {
                fileContextInfo.State = FileContextStates.NoneFilePath;
                fileContextInfo.Exception = new Exception("文件保存路径不能为空!");
                return fileContextInfo;
            }

            fileContextInfo.OldName = formFile.FileName;

            var fileExt = Path.GetExtension(formFile.FileName);
            if (!context.IsAllowAnyFile && !context.FileExtensions.Contains(fileExt.ToLower()))
            {
                fileContextInfo.State = FileContextStates.NoneExtensions;
                fileContextInfo.Exception = new Exception("不支持上传的文件格式!");
                return fileContextInfo;
            }

            if (formFile.Length > context.MaxFileSizeLimit)
            {
                fileContextInfo.State = FileContextStates.MaxLimit;
                fileContextInfo.Exception = new Exception("上传文件长度超限!");
                return fileContextInfo;
            }

            var hash = SHA256.Create();
            var hashBytes = await hash.ComputeHashAsync(formFile.OpenReadStream());
            var sha = BitConverter.ToString(hashBytes).Replace("-", "");
            var saveName = sha + fileExt;
            var fullPath = Path.Combine(context.FileDir, saveName);

            // 获取前缀和sha一样的文件
            // 如果hash一致，数据长度不一致起一个新的名字
            var exists = false;
            var filePaths = Directory.GetFiles(context.FileDir, sha + "*", SearchOption.TopDirectoryOnly);
            if (filePaths.Length > 0)
            {
                exists = filePaths.Any(w => new FileInfo(w).Length == formFile.Length);

                // 如果文件长度不一样,则文件名添加尾缀
                if (!exists)
                {
                    var index = 1;
                    var fileName = sha + $"_{index}" + fileExt;
                    while (File.Exists(Path.Combine(context.FileDir, fileName)))
                    {
                        index++;
                        fileName = sha + $"_{index}" + fileExt;
                    }

                    saveName = fileName;
                    fullPath = context.FileDir + saveName;
                }
            }

            if (exists)
            {
                fileContextInfo.State = FileContextStates.FileExists;
            }
            else
            {
                await using (var fs = File.Create(fullPath))
                {
                    await formFile.CopyToAsync(fs);
                    await fs.FlushAsync();
                }

                fileContextInfo.State = FileContextStates.Ok;
            }

            fileContextInfo.FileName = saveName;
            fileContextInfo.FullName = fullPath;
            fileContextInfo.FileRoot = context.ServerUrl + context.RequestPath + saveName;
            fileContextInfo.FileWebUrl = context.RequestPath + saveName;
            fileContextInfo.FileSize = formFile.Length.ToString();
            fileContextInfo.FileType = fileExt;
            fileContextInfo.Sha = sha;
        }
        catch (Exception ex)
        {
            fileContextInfo.State = FileContextStates.ErrorException;
            fileContextInfo.Exception = ex;
            throw fileContextInfo.Exception;
        }

        return fileContextInfo;
    }

    public static async Task<FileContextInfo> Success(this Task<FileContextInfo> contextInfo, Action<FileContextInfo> actionInfo)
    {
        return await contextInfo.SuccessAsync((fileContextInfo) =>
        {
            actionInfo(fileContextInfo);
            return Task.CompletedTask;
        });
    }

    public static async Task<FileContextInfo> Exists(this Task<FileContextInfo> contextInfo, Action<FileContextInfo> actionInfo)
    {
        return await contextInfo.ExistsAsync((fileContextInfo) =>
        {
            actionInfo(fileContextInfo);
            return Task.CompletedTask;
        });
    }

    public static async Task<FileContextInfo> Error(this Task<FileContextInfo> contextInfo, Action<FileContextInfo> actionInfo)
    {
        return await contextInfo.ErrorAsync((fileContextInfo) =>
        {
            actionInfo(fileContextInfo);
            return Task.CompletedTask;
        });
    }

    public static async Task<FileContextInfo> SuccessAsync(this Task<FileContextInfo> contextInfo, Func<FileContextInfo, Task> funcInfo)
    {
        var fileContextInfo = await contextInfo;
        if (fileContextInfo.State == FileContextStates.Ok)
        {
            await funcInfo(fileContextInfo);
        }

        return fileContextInfo;
    }

    public static async Task<FileContextInfo> ExistsAsync(this Task<FileContextInfo> contextInfo, Func<FileContextInfo, Task> funcInfo)
    {
        var fileContextInfo = await contextInfo;
        if (fileContextInfo.State == FileContextStates.FileExists)
        {
            await funcInfo(fileContextInfo);
        }

        return fileContextInfo;
    }

    public static async Task<FileContextInfo> ErrorAsync(this Task<FileContextInfo> contextInfo, Func<FileContextInfo, Task> funcInfo)
    {
        var fileContextInfo = new FileContextInfo();
        try
        {
            fileContextInfo = await contextInfo;
        }
        catch (Exception ex)
        {
            fileContextInfo.State = FileContextStates.ErrorException;
            fileContextInfo.Exception = ex;
            await funcInfo(fileContextInfo);
            return fileContextInfo;
        }

        if (fileContextInfo.State != FileContextStates.Ok && fileContextInfo.State != FileContextStates.FileExists)
        {
            await funcInfo(fileContextInfo);
        }

        return fileContextInfo;
    }
}